﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lidgren.Network;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using AwesomeGame.Weapons;

namespace AwesomeGame.Networking
{
    public class Client : IClient
    {
        public delegate void PlayerDeathEvent(long playerWhoDied, long playerWhoKilledThem);
        public delegate void SayEvent(string message);
        public delegate void PlayerListEvent(List<Player> players);
        public delegate void TankPositionEvent(Vector2 position, float rotation, long id);
        public delegate void TankFiredEvent(Vector2 position, float rotation, WeaponType weapon, long tankId, Guid firedEventId);
        public delegate void PlayerEvent(long id);
        public delegate void JoinEvent(Player player);
        public delegate void ProjectileEvent(Guid id);
        public delegate void ProjectileMovedEvent(Guid id, Vector2 position);
        public delegate void LocationEvent(Vector2 position);
        public event PlayerDeathEvent PlayerWasKilled;
        public event LocationEvent BombCreated;
        public event ProjectileMovedEvent MoveProjectile;
        public event ProjectileEvent RemoveProjectileEvent;
        public event TankPositionEvent TankPositionUpdated;
        public event TankFiredEvent TankFired;
        public event SayEvent MessageReceived;
        public event PlayerListEvent PlayerListUpdated;
        public event PlayerEvent LocalPlayerIdChanged;
        public event JoinEvent PlayerJoined;

        private NetClient _client;
        private string _serverAddress;
        private int _port;
        private bool _connected = false;
        private Player _localPlayer;

        public Client(string serverAddress, int port, ref Player localPlayer)
        {
            _serverAddress = serverAddress;
            _port = port;
            _localPlayer = localPlayer;
        }

        public void Start()
        {
            JoinServer(_serverAddress);
        }

        private void JoinServer(string serverAddress)
        {
            MessageReceived("Connecting to " + serverAddress + ":" + _port + "...");

            NetPeerConfiguration configuration = new NetPeerConfiguration("AwesomeGame");

            _client = new NetClient(configuration);
            _client.Start();
            _client.Connect(serverAddress, _port);
        }

        public void SendMessage(string message)
        {
            NetOutgoingMessage outgoingMessage = _client.CreateMessage();

            CommonReadsAndWrites.WriteMessage(message, ref outgoingMessage);            

            _client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void SendPlayerDeath(long playerWhoDied, long playerWhoKilledThem)
        {
            NetOutgoingMessage outgoingMessage = _client.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.PLAYER_DIED);
            outgoingMessage.Write(playerWhoDied);
            outgoingMessage.Write(playerWhoKilledThem);

            _client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void SendRemoveProjectileEvent(Guid bulletId)
        {
            NetOutgoingMessage outgoingMessage = _client.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.REMOVE_PROJECTILE);
            CommonReadsAndWrites.WriteGuid(bulletId, ref outgoingMessage);

            _client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void SendMoveProjectileEvent(Guid bulletId, Vector2 position)
        {
            NetOutgoingMessage outgoingMessage = _client.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.PROJECTILE_MOVED);
            CommonReadsAndWrites.WriteGuid(bulletId, ref outgoingMessage);
            CommonReadsAndWrites.WriteVector(position, ref outgoingMessage);

            _client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void SendJoinMessage(Player player)
        {
            NetOutgoingMessage outgoingMessage = _client.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.JOIN);
            CommonReadsAndWrites.WritePlayer(player, ref outgoingMessage);

            _client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void SendTankPosition(Vector2 position, float rotation, long tankId)
        {
            NetOutgoingMessage outgoingMessage = _client.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.SET_TANK_POSITION);
            CommonReadsAndWrites.WriteVector(position, ref outgoingMessage);
            outgoingMessage.Write(rotation);
            outgoingMessage.Write(tankId);

            _client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void SendTankFireEvent(Vector2 position, float rotation, WeaponType weaponType, long tankId, Guid fireEventId)
        {
            NetOutgoingMessage outgoingMessage = _client.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.TANK_FIRED);
            CommonReadsAndWrites.WriteVector(position, ref outgoingMessage);
            outgoingMessage.Write(rotation);
            outgoingMessage.Write((int)weaponType);
            outgoingMessage.Write(tankId);
            CommonReadsAndWrites.WriteGuid(fireEventId, ref outgoingMessage);

            _client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void SendBombCreatedEvent(Vector2 position)
        {
            NetOutgoingMessage outgoingMessage = _client.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.BOMB_DROPPED);
            CommonReadsAndWrites.WriteVector(position, ref outgoingMessage);

            _client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        private void ReceivedMessageFromServer(NetIncomingMessage incomingMessage)
        {
            string message = CommonReadsAndWrites.ReadMessage(incomingMessage);
            MessageReceived(message);
        }

        private void ReceivedRemoveProjectileEventServer(NetIncomingMessage incomingMessage)
        {
            Guid projectileId = CommonReadsAndWrites.ReadGuid(incomingMessage);
            RemoveProjectileEvent(projectileId);
        }

        private void ReceivedMoveProjectileEventServer(NetIncomingMessage incomingMessage)
        {
            Guid projectileId = CommonReadsAndWrites.ReadGuid(incomingMessage);
            Vector2 position = CommonReadsAndWrites.ReadVector(incomingMessage);

            MoveProjectile(projectileId, position);
        }

        private void ReceivedPlayerDiedFromServer(NetIncomingMessage incomingMessage)
        {
            long playerWhoDied = incomingMessage.ReadInt64();
            long playerWhoKilledThem = incomingMessage.ReadInt64();

            PlayerWasKilled(playerWhoDied, playerWhoKilledThem);
        }

        private void ReceivedFullPlayerListFromServer(NetIncomingMessage incomingMessage)
        {
            List<Player> players = CommonReadsAndWrites.ReadListOfPlayers(incomingMessage);
            PlayerListUpdated(players);
        }

        private void ReceivedPlayerIdFromServer(NetIncomingMessage incomingMessage)
        {
            long id = incomingMessage.ReadInt64();
            LocalPlayerIdChanged(id);
        }

        private void ReceivedPlayerJoinFromServer(NetIncomingMessage incomingMessage)
        {
            Player player = CommonReadsAndWrites.ReadPlayer(incomingMessage);
            PlayerJoined(player);
        }

        private void ReceivedTankPositionFromServer(NetIncomingMessage incomingMessage)
        {
            Vector2 position = CommonReadsAndWrites.ReadVector(incomingMessage);
            float rotation = incomingMessage.ReadSingle();
            long id = incomingMessage.ReadInt64();

            TankPositionUpdated(position, rotation, id);
        }

        private void ReceivedTankFiredEventFromServer(NetIncomingMessage incomingMessage)
        {            
            Vector2 position = CommonReadsAndWrites.ReadVector(incomingMessage);
            float rotation = incomingMessage.ReadSingle();
            WeaponType weapon = (WeaponType)incomingMessage.ReadInt32();
            long tankId = incomingMessage.ReadInt64();
            Guid fireEventId = CommonReadsAndWrites.ReadGuid(incomingMessage);

            TankFired(position, rotation, weapon, tankId, fireEventId);
        }

        private void ReceivedBombCreatedEventFromServer(NetIncomingMessage incomingMessage)
        {
            Vector2 position = CommonReadsAndWrites.ReadVector(incomingMessage);

            BombCreated(position);
        }

        public void ParseNetworkInput(NetIncomingMessage message, bool isServer)
        {
            DataMessageTypes dataType = (DataMessageTypes)message.ReadInt32();

            if (dataType == DataMessageTypes.SAY)
                ReceivedMessageFromServer(message);
            else if (dataType == DataMessageTypes.FULL_PLAYER_LIST)
                ReceivedFullPlayerListFromServer(message);
            else if (dataType == DataMessageTypes.SET_PLAYER_ID)
                ReceivedPlayerIdFromServer(message);
            else if (dataType == DataMessageTypes.JOIN)
                ReceivedPlayerJoinFromServer(message);
            else if (dataType == DataMessageTypes.SET_TANK_POSITION)
                ReceivedTankPositionFromServer(message);
            else if (dataType == DataMessageTypes.TANK_FIRED)
                ReceivedTankFiredEventFromServer(message);
            else if (dataType == DataMessageTypes.REMOVE_PROJECTILE)
                ReceivedRemoveProjectileEventServer(message);
            else if (dataType == DataMessageTypes.BOMB_DROPPED)
                ReceivedBombCreatedEventFromServer(message);
            else if (dataType == DataMessageTypes.PLAYER_DIED)
                ReceivedPlayerDiedFromServer(message);
            else if (dataType == DataMessageTypes.PROJECTILE_MOVED)
                ReceivedMoveProjectileEventServer(message);
        }

        public void Update()
        {
            if (_connected == false)
            {
                if (_client.ConnectionStatus == NetConnectionStatus.Connected)
                {
                    SendJoinMessage(_localPlayer);
                    _connected = true;
                    MessageReceived("You are now connected.");
                }
            }

            NetIncomingMessage msg;
            while ((msg = _client.ReadMessage()) != null)
            {
                switch (msg.MessageType)
                {
                    case NetIncomingMessageType.VerboseDebugMessage:
                    case NetIncomingMessageType.DebugMessage:
                    case NetIncomingMessageType.WarningMessage:
                    case NetIncomingMessageType.ErrorMessage:
                        Debug.WriteLine(msg.ReadString());
                        break;
                    case NetIncomingMessageType.Data:
                        ParseNetworkInput(msg, false);
                        break;
                    default:
                        break;
                }
                _client.Recycle(msg);
            }
        }
    }
}
